import { Node, ScrollView, v3, _decorator, UITransform, isValid } from 'cc';
import { BaseView } from './../../rxjs/cc3/BaseView';
import { CCBindScrollView } from '../../rxjs/cc3/BindScrollView';
import { forkJoin, Observable, of } from 'rxjs';
import { optimizeScrollView } from './OptimizeScrollView';

const { ccclass, property } = _decorator;

/**
 * 使用说明：
 * 
 * 继承 LeanDynamicScrollView 写一个 XxxScrollView
 * 重写方法：
 * getDataCount (总大小，也就是总行数，放心不会全部显示出来，只是用于计算位置)
 * getItemNode
 * putItemNode
 * putAllChildren(可以省)
 * refreshRow 用来具体刷新item
 * 
 * 做好item的prefab，
 * 做出原本scrollview的node，再其上挂载 XxxScrollView
 * contentNode挂上 scrollView里面的content
 * 
 * content里面可以尝试摆item，可以加上layout进行尝试，但最后一定要关闭这个layout或者直接删掉layout
 * 该layout中的padding top/bottom 整好对应填入 XxxScrollView的 HeadHeight TailHeight
 * item的prefab的高度填入XxxScrollView的ItemSize
 * 
 * 注意：ScrollView下面的view节点（也就是content的父节点）的锚点要设置为(0.5, 0.5)，否则会出现展示偏移
 * 
 * 开启:
 * 因为默认这个是空的关闭状态，啥也不显示，所以需要调用开启才会显示。
 * 我经常在panel的willOpen里面调用 openScrollView() 即可。
 * 
 * 此时item应该已经能正常显示了
 * 
 * 最后补上刷新item的方法：
 *    refreshRow(), 它里面通过 this.currentItems[idx] 就能获取对应idx行的node，想怎么刷就怎么刷了
 * 
 * 滑动手感:
 * 下面Feeling Optimized勾选之后，原break应该设置0.9以上，比如0.95
 * 此时如果使用了onLoadView，则应该调用父类的onLoadView, super.onLoadView();
 * 
 * 数据源改变后调用这个进行刷新：
 * dataSourceChanged()
 * 
 * 需要动画的，就重写方法 itemIn(idx: number, order: number)
 */

@ccclass
export default class LeanDynamicScrollView extends BaseView {

    @property(ScrollView)
    scrollView: ScrollView = null;

    @property(Node)
    contentNode: Node = null;

    @property
    itemSize: number = 0;

    @property
    headLength: number = 0;

    @property
    tailLength: number = 0;

    @property
    feelingOptimized: boolean = true;

    currentItems: Record<number, Node> = {};

    isGenerated: boolean = false;

    onLoadView() {
        this.feelingOptimized && optimizeScrollView(this.scrollView);
    }

    onDestroyView() {
    }

    useObserves() {
        return [
            CCBindScrollView(this.scrollView).subscribe(_ => {
                if (_.state == 'scrolling') this.scrollViewScrolling();
            })
        ]
    }

    scrollViewScrolling() {
        if (!this.isOpen) return;
        // 检查该回收和新显示的节点
        let _tempCurrentRow: Record<number, Node> = {};
        for (let key in this.currentItems) {
            if (this.idxNeedShow(Number(key))) {
                _tempCurrentRow[key] = this.currentItems[key];
            } else {
                this.recycleRow(Number(key));
            }
        }
        this.currentItems = _tempCurrentRow;
        this.traverseNeedShow(idx => {
            if (this.currentItems[idx] == null) {
                this.genRow(idx);
            }
        });
    }

    isOpen: boolean = false;

    // 开启列表，开始生成项
    openScrollView() {
        if (!this.isGenerated) {
            this.generateRows();
            this.isOpen = true;
        }
    }

    // 关闭列表，删除列表项（注意是异步可观察）
    closeScrollViewObservable() {
        this.isOpen = false;
        return this.recycleAllObservable();
    }

    // 继承后覆盖，给出列表总大小
    getDataCount() {
        return 1;
    }

    // 对象池相关，需要重写。回收container所有的item节点
    putAllChildren(container: Node) {
        container.destroyAllChildren();
    }

    // 对象池相关，需要重写。从池中获得一个Item节点
    getItemNode(): Node {
        return new Node();
    }

    // 对象池相关，需要重写。向池中返还一个Item节点
    putItemNode(node: Node) {
        node.destroy();
    }

    generateRows() {
        this.traverseNeedShow((idx, order) => {
            this.genRow(idx, order);
        });
        this.refreshContentSize();
        this.isGenerated = true;
    }

    refreshContentSize() {
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            let contentHeight = this.headLength + this.itemSize * this.getDataCount() + this.tailLength;
            this.contentNode.getComponent(UITransform).height = contentHeight;
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            let contentWidth = this.headLength + this.itemSize * this.getDataCount() + this.tailLength;
            this.contentNode.getComponent(UITransform).width = contentWidth;
        } else {
            this.warn('不支持同时两个轴向的运动');
        }
    }

    getFirstNeedShowProgress() {
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            let start_p = this.scrollView.getComponent(UITransform).height / 2 + this.headLength;
            let diff = this.contentNode.position.y - start_p;
            let idx = Math.floor(diff / this.itemSize);
            return idx;
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            let start_p = -this.scrollView.getComponent(UITransform).width / 2 - this.headLength;
            let diff = -this.contentNode.position.x + start_p;
            let idx = Math.floor(diff / this.itemSize);
            return idx;
        }
        return null;
    }

    getLastNeedShowProgress() {
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            let end_p = -this.scrollView.getComponent(UITransform).height / 2 + this.headLength;
            let diff = this.contentNode.position.y - end_p;
            let idx = Math.floor(diff / this.itemSize);
            return idx;
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            let end_p = this.scrollView.getComponent(UITransform).width / 2 - this.headLength;
            let diff = -this.contentNode.position.x + end_p;
            let idx = Math.floor(diff / this.itemSize);
            return idx;
        }
    }

    /** 遍历需要显示的节点的progress */
    traverseNeedShow(call: (idx: number, order: number) => void) {
        let firstIdx = this.getFirstNeedShowProgress();
        let lastIdx = this.getLastNeedShowProgress();
        let order = 0;
        for (let idx = firstIdx; idx <= lastIdx; idx++) {
            if (idx >= 0 && idx < this.getDataCount()) {
                call(idx, order);
                order++;
            }
        }
    }

    traverseRNeedShow(call: (idx: number, order: number) => void) {
        let firstIdx = this.getFirstNeedShowProgress();
        let lastIdx = this.getLastNeedShowProgress();
        let order = 0;
        for (let idx = lastIdx; idx >= firstIdx; idx--) {
            if (idx >= 0 && idx < this.getDataCount()) {
                call(idx, order);
                order++;
            }
        }
    }

    idxNeedShow(idx: number): boolean {
        let firstIdx = this.getFirstNeedShowProgress();
        let lastIdx = this.getLastNeedShowProgress();
        if (idx >= firstIdx && idx <= lastIdx) {
            if (idx >= 0 && idx < this.getDataCount()) {
                return true;
            }
        }
        return false;
    }

    // 项的中点对齐
    yPosByProgress(idx: number) {
        return -this.headLength - (idx + .5) * this.itemSize;
    }

    // 项的中点对齐
    xPosByProgress(idx: number) {
        return this.headLength + (idx + .5) * this.itemSize;
    }

    refreshShowed() {
        this.traverseNeedShow(idx => {
            if (this.currentItems[idx] == null) {
                this.genRow(idx);
            } else {
                this.refreshRow(idx);
            }
        });
    }

    clear() {
        this.putAllChildren(this.contentNode);
        this.currentItems = {};
        this.isGenerated = false;
    }

    genRow(idx: number, order: number = 0) {
        let node = this.getItemNode();
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            node.setPosition(v3(0, this.yPosByProgress(idx), 0));
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            node.setPosition(v3(this.xPosByProgress(idx), 0, 0));
        }
        node.setParent(this.contentNode);
        this.currentItems[idx] = node;
        this.refreshRow(idx);
        this.itemIn(idx, order);
    }

    recycleRow(idx: number) {
        let node = this.currentItems[idx];
        delete this.currentItems[idx];
        this.putItemNode(node);
    }

    recycleRowObservable(idx: number, order: number) {
        return new Observable(_ => {
            this.itemOut(idx, order, () => {
                if (isValid(this?.node) && this.currentItems != null) { // challenge 页面项动画的加强 MrZ 28751 TypeError
                    let node = this.currentItems[idx]; // err 这里可能为空 frontjs
                    delete this.currentItems[idx];
                    this.putItemNode(node);
                    _.next(null);
                    _.complete();
                }
            });
        });
    }

    recycleAllObservable() {
        let observables: Observable<any>[] = [];
        this.traverseRNeedShow((idx, order) => {
            if (this.currentItems[idx] == null) {
            } else {
                observables.push(this.recycleRowObservable(idx, order));
            }
        });
        if (observables.length == 0) return of(null);
        return forkJoin(observables);
    }

    itemIn(idx: number, order: number) { }

    itemOut(idx: number, order: number, recycleCallback: () => void) { recycleCallback(); }

    // 继承后覆盖这个方法，刷新item
    refreshRow(idx: number) {
    }

    /** 如果数据源改变了，就调用这个 */
    dataSourceChanged(ignoreScroll?: boolean) {
        if (!this.isOpen) {
            return;
        }
        this.clear();
        this.refreshContentSize();
        this.refreshShowed();
        this.scrollViewScrolling();
        if (ignoreScroll) this.scrollView.scrollToTop(); // 似乎不好用，外层改用下面的setContentPosByIdx
    }

    // 辅助功能,idx放在第一位时的content的位置
    // ? 自己也解释不通常量里面126的原因，应该是从结果反算的
    getContentPosByIdx(idx: number) {
        let scrollViewSize = null;
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            scrollViewSize = this.getComponent(UITransform).height;
            let min = scrollViewSize / 2; //-376;
            let max = this.getDataCount() * this.itemSize + this.tailLength - scrollViewSize / 2;
            let target = -126 - idx * this.itemSize;
            target = Math.max(min, target);
            target = Math.min(max, target);
            return { min, max, target };
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            scrollViewSize = this.getComponent(UITransform).width;
            let min = -(this.getDataCount() * this.itemSize + this.tailLength - scrollViewSize / 2);
            let max = -scrollViewSize / 2; //-376;
            let target = -126 - idx * this.itemSize;
            target = Math.max(min, target);
            target = Math.min(max, target);
            return { min, max, target };
        }
    }

    // 直接设置位置
    setContentPosByIdx(idx: number) {
        if (this.scrollView.vertical && !this.scrollView.horizontal) {
            this.contentNode.setY(this.getContentPosByIdx(idx).target);
        } else if (!this.scrollView.vertical && this.scrollView.horizontal) {
            this.contentNode.setX(this.getContentPosByIdx(idx).target);
        }
        this.scrollViewScrolling();
    }
}
